/*
* "$Id: mxml-string.c 424 2010-12-25 16:21:50Z mike $"
*
* String functions for Mini-XML, a small XML-like file parsing library.
*
* Copyright 2003-2010 by Michael R Sweet.
*
* These coded instructions, statements, and computer programs are the
* property of Michael R Sweet and are protected by Federal copyright
* law.  Distribution and use rights are outlined in the file "COPYING"
* which should have been included with this file.  If this file is
* missing or damaged, see the license at:
*
*     http://www.minixml.org/
*
* Contents:
*
*   _mxml_snprintf()  - Format a string.
*   _mxml_strdup()    - Duplicate a string.
*   _mxml_strdupf()   - Format and duplicate a string.
*   _mxml_vsnprintf() - Format a string into a fixed size buffer.
*   _mxml_vstrdupf()  - Format and duplicate a string.
*/

/*
* Include necessary headers...
*/

#include "mxml-config.h"


/*
* The va_copy macro is part of C99, but many compilers don't implement it.
* Provide a "direct assignment" implmentation when va_copy isn't defined...
*/

#ifndef va_copy
#  ifdef __va_copy
#    define va_copy(dst,src) __va_copy(dst,src)
#  else
#    define va_copy(dst,src) memcpy(&dst, &src, sizeof(va_list))
#  endif /* __va_copy */
#endif /* va_copy */


#ifndef HAVE_SNPRINTF
/*
* '_mxml_snprintf()' - Format a string.
*/

int					/* O - Number of bytes formatted */
	_mxml_snprintf(char       *buffer,	/* I - Output buffer */
	size_t     bufsize,	/* I - Size of output buffer */
	const char *format,	/* I - Printf-style format string */
	...)			/* I - Additional arguments as needed */
{
	va_list	ap;			/* Argument list */
	int		bytes;			/* Number of bytes formatted */


	va_start(ap, format);
	bytes = vsnprintf(buffer, bufsize, format, ap);
	va_end(ap);

	return (bytes);
}
#endif /* !HAVE_SNPRINTF */


/*
* '_mxml_strdup()' - Duplicate a string.
*/

#ifndef HAVE_STRDUP
char *					/* O - New string pointer */
	_mxml_strdup(const char *s)		/* I - String to duplicate */
{
	char	*t;				/* New string pointer */


	if (s == NULL)
		return (NULL);

	if ((t = malloc(strlen(s) + 1)) == NULL)
		return (NULL);

	return (strcpy(t, s));
}
#endif /* !HAVE_STRDUP */


/*
* '_mxml_strdupf()' - Format and duplicate a string.
*/

char *					/* O - New string pointer */
	_mxml_strdupf(const char *format,	/* I - Printf-style format string */
	...)			/* I - Additional arguments as needed */
{
	va_list	ap;			/* Pointer to additional arguments */
	char		*s;			/* Pointer to formatted string */


	/*
	* Get a pointer to the additional arguments, format the string,
	* and return it...
	*/

	va_start(ap, format);
	s = _mxml_vstrdupf(format, ap);
	va_end(ap);

	return (s);
}


#ifndef HAVE_VSNPRINTF
/*
* '_mxml_vsnprintf()' - Format a string into a fixed size buffer.
*/

int					/* O - Number of bytes formatted */
	_mxml_vsnprintf(char       *buffer,	/* O - Output buffer */
	size_t     bufsize,	/* O - Size of output buffer */
	const char *format,	/* I - Printf-style format string */
	va_list    ap)		/* I - Pointer to additional arguments */
{
	char		*bufptr,		/* Pointer to position in buffer */
		*bufend,		/* Pointer to end of buffer */
		sign,			/* Sign of format width */
		size,			/* Size character (h, l, L) */
		type;			/* Format type character */
	int		width,			/* Width of field */
		prec;			/* Number of characters of precision */
	char		tformat[100],		/* Temporary format string for sprintf() */
		*tptr,			/* Pointer into temporary format */
		temp[1024];		/* Buffer for formatted numbers */
	char		*s;			/* Pointer to string */
	int		slen;			/* Length of string */
	int		bytes;			/* Total number of bytes needed */


	/*
	* Loop through the format string, formatting as needed...
	*/

	bufptr = buffer;
	bufend = buffer + bufsize - 1;
	bytes  = 0;

	while (*format)
	{
		if (*format == '%')
		{
			tptr = tformat;
			*tptr++ = *format++;

			if (*format == '%')
			{
				if (bufptr && bufptr < bufend) *bufptr++ = *format;
				bytes ++;
				format ++;
				continue;
			}
			else if (strchr(" -+#\'", *format))
			{
				*tptr++ = *format;
				sign = *format++;
			}
			else
				sign = 0;

			if (*format == '*')
			{
				/*
				* Get width from argument...
				*/

				format ++;
				width = va_arg(ap, int);

				snprintf(tptr, sizeof(tformat) - (tptr - tformat), "%d", width);
				tptr += strlen(tptr);
			}
			else
			{
				width = 0;

				while (isdigit(*format & 255))
				{
					if (tptr < (tformat + sizeof(tformat) - 1))
						*tptr++ = *format;

					width = width * 10 + *format++ - '0';
				}
			}

			if (*format == '.')
			{
				if (tptr < (tformat + sizeof(tformat) - 1))
					*tptr++ = *format;

				format ++;

				if (*format == '*')
				{
					/*
					* Get precision from argument...
					*/

					format ++;
					prec = va_arg(ap, int);

					snprintf(tptr, sizeof(tformat) - (tptr - tformat), "%d", prec);
					tptr += strlen(tptr);
				}
				else
				{
					prec = 0;

					while (isdigit(*format & 255))
					{
						if (tptr < (tformat + sizeof(tformat) - 1))
							*tptr++ = *format;

						prec = prec * 10 + *format++ - '0';
					}
				}
			}
			else
				prec = -1;

			if (*format == 'l' && format[1] == 'l')
			{
				size = 'L';

				if (tptr < (tformat + sizeof(tformat) - 2))
				{
					*tptr++ = 'l';
					*tptr++ = 'l';
				}

				format += 2;
			}
			else if (*format == 'h' || *format == 'l' || *format == 'L')
			{
				if (tptr < (tformat + sizeof(tformat) - 1))
					*tptr++ = *format;

				size = *format++;
			}

			if (!*format)
				break;

			if (tptr < (tformat + sizeof(tformat) - 1))
				*tptr++ = *format;

			type  = *format++;
			*tptr = '\0';

			switch (type)
			{
			case 'E' : /* Floating point formats */
			case 'G' :
			case 'e' :
			case 'f' :
			case 'g' :
				if ((width + 2) > sizeof(temp))
					break;

				sprintf(temp, tformat, va_arg(ap, double));

				bytes += strlen(temp);

				if (bufptr)
				{
					if ((bufptr + strlen(temp)) > bufend)
					{
						strncpy(bufptr, temp, (size_t)(bufend - bufptr));
						bufptr = bufend;
					}
					else
					{
						strcpy(bufptr, temp);
						bufptr += strlen(temp);
					}
				}
				break;

			case 'B' : /* Integer formats */
			case 'X' :
			case 'b' :
			case 'd' :
			case 'i' :
			case 'o' :
			case 'u' :
			case 'x' :
				if ((width + 2) > sizeof(temp))
					break;

#ifdef HAVE_LONG_LONG
				if (size == 'L')
					sprintf(temp, tformat, va_arg(ap, long long));
				else
#endif /* HAVE_LONG_LONG */
					sprintf(temp, tformat, va_arg(ap, int));

				bytes += strlen(temp);

				if (bufptr)
				{
					if ((bufptr + strlen(temp)) > bufend)
					{
						strncpy(bufptr, temp, (size_t)(bufend - bufptr));
						bufptr = bufend;
					}
					else
					{
						strcpy(bufptr, temp);
						bufptr += strlen(temp);
					}
				}
				break;

			case 'p' : /* Pointer value */
				if ((width + 2) > sizeof(temp))
					break;

				sprintf(temp, tformat, va_arg(ap, void *));

				bytes += strlen(temp);

				if (bufptr)
				{
					if ((bufptr + strlen(temp)) > bufend)
					{
						strncpy(bufptr, temp, (size_t)(bufend - bufptr));
						bufptr = bufend;
					}
					else
					{
						strcpy(bufptr, temp);
						bufptr += strlen(temp);
					}
				}
				break;

			case 'c' : /* Character or character array */
				bytes += width;

				if (bufptr)
				{
					if (width <= 1)
						*bufptr++ = va_arg(ap, int);
					else
					{
						if ((bufptr + width) > bufend)
							width = bufend - bufptr;

						memcpy(bufptr, va_arg(ap, char *), (size_t)width);
						bufptr += width;
					}
				}
				break;

			case 's' : /* String */
				if ((s = va_arg(ap, char *)) == NULL)
					s = "(null)";

				slen = strlen(s);
				if (slen > width && prec != width)
					width = slen;

				bytes += width;

				if (bufptr)
				{
					if ((bufptr + width) > bufend)
						width = bufend - bufptr;

					if (slen > width)
						slen = width;

					if (sign == '-')
					{
						strncpy(bufptr, s, (size_t)slen);
						memset(bufptr + slen, ' ', (size_t)(width - slen));
					}
					else
					{
						memset(bufptr, ' ', (size_t)(width - slen));
						strncpy(bufptr + width - slen, s, (size_t)slen);
					}

					bufptr += width;
				}
				break;

			case 'n' : /* Output number of chars so far */
				*(va_arg(ap, int *)) = bytes;
				break;
			}
		}
		else
		{
			bytes ++;

			if (bufptr && bufptr < bufend)
				*bufptr++ = *format;

			format ++;
		}
	}

	/*
	* Nul-terminate the string and return the number of characters needed.
	*/

	*bufptr = '\0';

	return (bytes);
}
#endif /* !HAVE_VSNPRINTF */


/*
* '_mxml_vstrdupf()' - Format and duplicate a string.
*/

char *					/* O - New string pointer */
	_mxml_vstrdupf(const char *format,	/* I - Printf-style format string */
	va_list    ap)		/* I - Pointer to additional arguments */
{
	int		bytes;			/* Number of bytes required */
	char		*buffer,		/* String buffer */
		temp[256];		/* Small buffer for first vsnprintf */
	va_list	apcopy;			/* Copy of argument list */


	/*
	* First format with a tiny buffer; this will tell us how many bytes are
	* needed...
	*/

	va_copy(apcopy, ap);
	bytes = vsnprintf(temp, sizeof(temp), format, apcopy);

	if (bytes < sizeof(temp))
	{
		/*
		* Hey, the formatted string fits in the tiny buffer, so just dup that...
		*/

		return (strdup(temp));
	}

	/*
	* Allocate memory for the whole thing and reformat to the new, larger
	* buffer...
	*/

	if ((buffer = calloc(1, bytes + 1)) != NULL)
		vsnprintf(buffer, bytes + 1, format, ap);

	/*
	* Return the new string...
	*/

	return (buffer);
}


/*
* End of "$Id: mxml-string.c 424 2010-12-25 16:21:50Z mike $".
*/
